const Text = Symbol() // 文本节点
const Comment = Symbol() // 注释节点
const Fragment = Symbol() // Fragment
function createRender(options) {
  const {
    createElement,
    insert,
    setElementText,
    patchProps,
    createText,
    setText,
    createComment,
  } = options
  // 卸载函数
  function unmount(vnode) {
    if (vnode.type === Fragment) {
      vnode.children.forEach((c) => unmount(c))
      return
    }
    const parent = vnode.el.parentNode
    if (parent) {
      parent.removeChild(vnode.el)
    }
  }
  // 挂载
  function mountElement(vnode, container) {
    // 以vnode.type的值作为标签名创建新的DOM元素
    const el = (vnode.el = createElement(vnode.type))
    // 处理子节点，如果说子节点是字符串，那么代表元素具有文本节点
    if (typeof vnode.children === 'string') {
      // 这时只需要设置元素的textContent即可
      setElementText(el, vnode.children)
    } else if (Array.isArray(vnode.children)) {
      // 遍历每一个子节点，并调用patch函数挂载它们
      vnode.children.forEach((child) => {
        patch(null, child, el)
      })
    }

    if (vnode.props) {
      for (const key in vnode.props) {
        patchProps(el, key, null, vnode.props[key])
      }
    }

    // 将创建好的DOM元素添加到容器中去
    insert(el, container)
    // 这样就完成了一个vnode的挂载
  }

  // 更新DOM元素操作
  function patchElement(n1, n2) {
    const el = (n2.el = n1.el)
    const oldProps = n1.props
    const newProps = n2.props
    // 第一步更新props
    for (const key in newProps) {
      if (
        oldProps &&
        oldProps.hasOwnProperty(key) &&
        newProps[key] !== oldProps[key]
      ) {
        patchProps(el, key, oldProps[key], newProps[key])
      }
    }

    for (const key in oldProps) {
      if (!(key in newProps)) {
        patchProps(el, key, oldProps[key], null)
      }
    }

    // 第二步，更新children
    patchChildren(n1, n2, el)
  }

  // 更新子节点
  function patchChildren(n1, n2, container) {
    // 判断新子节点的类型是否是文本节点
    if (typeof n2.children === 'string') {
      // 旧子节点的类型有三种可能：没有子节点、文本子节点和一组子节点
      // 只有当旧的子节点为一组子节点时，才需要逐个卸载，其他情况下什么都不需要做
      if (Array.isArray(n1.children)) {
        n1.children.forEach((c) => unmount(c))
      }
      setElementText(container, n2.children)
    } else if (Array.isArray(n2.children)) {
      if (Array.isArray(n1.children)) {
        // 代码运行到这里的话，说明新旧子节点都是一组子节点，这里涉及核心的diff算法
        // 旧子节点
        const oldChildren = n1.children
        // 新子节点
        const newChildren = n2.children
        // 找到可复用的子节点，并且打补丁
        for (let i = 0; i< newChildren.length; i++) {
          const newVNode = newChildren[i]
          for (let j = 0; j < oldChildren.length; j++) {
            const oldVNode = oldChildren[i]
            if (newVNode.key === oldVNode.key) {
              patch(oldVNode, newVNode, container)
              break
            }
          }
        }
        // 用来存储寻找过程中遇到的最大索引值
        let lastIndex = 0
        for (let i = 0; i< newChildren.length; i++) {
          const newVNode = newChildren[i]
          let j = 0;
          for (j; j < oldChildren.length; j++) {
            const oldVNode = oldChildren[j]
            if (newVNode.key === oldVNode.key) {
              patch(oldVNode, newVNode, container)
              if (j < lastIndex) {
                // 如果当前找到的节点在旧children中的索引小于最大索引值lastIndex,
                // 说明该节点对应的真实DOM需要移动
                // 拿到新子节点的前一个vnode，也就是prevVNode
                const prevVNode = newChildren[i-1]
                // 如果说prevVNode不存在，说明当前newVNode是第一个，不需要移动
                if (prevVNode) {
                  // 由于我们要将newVNode对应的真实DOM移动到prevVNode所对应的真实DOM的后面，
                  // 所以我们需要获取prevVNode所对应真实DOM的下一个兄弟节点，并将其作为锚点
                  const anchor = prevVNode.el.nextSibling
                  insert(newVNode.el, container, anchor)
                }
              }else {
                // 如果当前找到的节点在旧children中的索引不小于最大索引值
                // 则更新lastIndex的值
                lastIndex = j
              }
              break
            }
          }
        }
        // 旧的一组子节点的长度
        const oldLen = oldChildren.length
        // 新的一组子节点的长度
        const newLen = newChildren.length
        // 两组子节点的公共长度，即两者中较短的那一组子节点的长度
        const commonLength = Math.min(oldLen, newLen)
        for (let i = 0; i < commonLength; i++) {
          // 调用patch函数逐个更新子节点
          patch(oldChildren[i], newChildren[i], container)
        }
        // 如果新的一组子节点的长度大于了旧的一组子节点的长度，说明有节点需要挂载
        if (newLen > oldLen) {
          for (let i = commonLength; i < newLen; i++) {
            patch(null, newChildren[i], container)
          }
        } else if (newLen < oldLen) {
          // 如果oldLen > newLen 说明有节点需要卸载
          for(let i = commonLength; i < oldLen; i++) {
            unmount(oldChildren[i])
          }
        }
        
      } else {
        // 此时旧子节点要么为空，要么就是文本子节点
        // 不管哪种情况，都应该先将容器清空
        setElementText(container, '')
        // 然后将新的一组子节点逐个挂载
        n2.children.forEach((c) => patch(null, c, container))
      }
    } else {
      // 代码运行到这里的时候，说明新子节点不存在
      // 旧子节点是一组子节点的话，只需要逐个卸载即可
      if (Array.isArray(n1.children)) {
        n1.children.forEach((c) => unmount(c))
      } else if (typeof n1.children === 'string') {
        // 旧子节点是文本节点的话，直接清空内容即可
        setElementText(container, '')
      } else {
        // 如果也没有旧子节点，则什么都不用做
      }
    }
  }

  function patch(n1, n2, container) {
    // 如果两个vnode的元素类型不同，直接卸载旧的，然后再向下执行，挂载新的
    if (n1 && n1.type !== n2.type) {
      unmount(n1)
      n1 = null
    }
    const { type } = n2
    if (typeof type === 'string') {
      // 如果说旧节点不存在，那么就意味着是挂载，调用mountElement方法
      if (!n1) {
        mountElement(n2, container)
      } else {
        // 如果有旧节点，那么意味着打补丁
        patchElement(n1, n2)
      }
    } else if (typeof type === 'object') {
      // n2.type的值类型是对象，那么说明是一个组件
    } else if (type === Text) {
      // 处理其他类型的vnode
      if (!n1) {
        // 使用createTextNode创建文本节点
        const el = (n2.el = createText(n2.children))
        // 将文本节点插入到容器中
        insert(el, container)
      } else {
        const el = (n2.el = n1.el)
        if (n2.children !== n1.children) {
          setText(el, n2.children)
        }
      }
    } else if (type === Comment) {
      if (!n1) {
        // 使用createTextNode创建文本节点
        const el = (n2.el = createComment(n2.children))
        // 将文本节点插入到容器中
        insert(el, container)
      } else {
        const el = (n2.el = n1.el)
        if (n2.children !== n1.children) {
          setText(el, n2.children)
        }
      }
    } else if (type === Fragment) {
      if (!n1) {
        n2.children.forEach((c) => patch(null, c, container))
      } else {
        patchChildren(n1, n2, container)
      }
    }
  }
  function render(vnode, container) {
    if (vnode) {
      // 新vnode存在，并且旧的vnode一起传递给patch函数，进行打补丁
      patch(container._vnode, vnode, container)
    } else {
      if (container._vnode) {
        // 旧vnode存在，且新vnode不存在，说明是卸载
        // 只需要将container内的dom清空即可
        // 拿到真实DOM
        // 拿到真实DOM的父元素
        // 从父元素中移除
        unmount(container._vnode)
      }
    }
    // 把vnode存储到container的_vnode瞎，也就是后续渲染中的旧vnode
    container._vnode = vnode
  }
  return {
    render,
  }
}
